package weka.clusterers;

import java.io.*;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;


import weka.core.Instances;
import weka.core.converters.ArffSaver;
import weka.core.converters.CSVLoader;

import weka.clusterers.Clusterer;
import weka.clusterers.DBScan;
import weka.clusterers.Groups;
import weka.core.Attribute;
import weka.core.FastVector;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.Remove;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import com.martiansoftware.jsap.FlaggedOption;
import com.martiansoftware.jsap.JSAP;
import com.martiansoftware.jsap.JSAPResult;
import com.martiansoftware.jsap.Switch;



public class GroupsAllFrames {
	
	
	
	public static int countFiles(String pathFile) throws IOException {
		int count=0;
		File path = new File(pathFile);
		File [] files = path.listFiles();
	    for (int i = 0; i < files.length; i++){
	        if (files[i].isFile() && !files[i].isHidden()){ //this line weeds out other directories/folders and  files marked as deleted
	            count++;
	        }
	    }
	    return count;
	}
	
	
	public static int countLines(String filename) throws IOException {
	    LineNumberReader reader  = new LineNumberReader(new FileReader(filename));
	int cnt = 0;
	String lineRead = "";
	while ((lineRead = reader.readLine()) != null) {}

	cnt = reader.getLineNumber(); 
	reader.close();
	return cnt;
	}

	

	private static Instances readData(String nameFile) {
		Instances data=null;
		try {
		    // load CSV
		    CSVLoader loader = new CSVLoader();
		    loader.setSource(new File(nameFile));
		    data = loader.getDataSet();
		    

			
			Enumeration<Instance> enumeration = data.enumerateInstances();
			while(enumeration.hasMoreElements()) {
				Instance instance2 = enumeration.nextElement();
				
				Double x = instance2.value(0);
				Double y = instance2.value(1);
				Double z = instance2.value(2);
	//TS			Double t = instance2.value(3);
	//TS			System.out.println("X="+x+" Y="+y+" Z="+z+" T="+t);

				//System.out.println("X="+x+" Y="+y+" Z="+z);
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return data;
	}
	
	private static int totalLines(String nameFile) {
		int totLines=0;
		try{
			 FileInputStream fstreamIn = new FileInputStream(nameFile);
			  DataInputStream in = new DataInputStream(fstreamIn);
			  BufferedReader br = new BufferedReader(new InputStreamReader(in));
			  String strLine="";

			  while ((strLine = br.readLine()) != null)   {
				  totLines++;
			  }
			  in.close();
			 
		 }catch (Exception e){//Catch exception if any
			  System.err.println("Error: " + e.getMessage());
		  }
		 return totLines-1;
	}
	
	private static Double [][] readDataCSV(String nameFile,int numFeatures,int [][] positionPersons,int f,int  totalFrames,int windowSize, int totalPersons, boolean avgPoint) {
		int rows;
		if (avgPoint) {
			rows=countPersons(positionPersons,f,totalFrames,windowSize,totalPersons); // retorna el numero de personas que hay a partir del frame f hasta (f + windowsize)
																					  // positionPersons is a binary matrix indicando donde hay 3d posicion  para una persona.
																					 //positionPerson is a matrix totalFrames X totalPersons 
		}
		else{  // read all lines of the file, es decir todas las personas que estan en un solo frame.
			rows=totalLines(nameFile);
		}
		Double matrix[][]=Groups.createDoubleMatrix(rows,numFeatures);	
		
		try{
				  FileInputStream fstreamIn = new FileInputStream(nameFile);
				  DataInputStream in = new DataInputStream(fstreamIn);
				  BufferedReader br = new BufferedReader(new InputStreamReader(in));
				  String strLine=br.readLine(); // read  name features
				  String temp[];
				  if (!avgPoint) {
						
					  int i=0;
					  while ((strLine = br.readLine()) != null)   {
						  temp=strLine.split(",");
						  //System.out.println("xxxxxxxx"+strLine);
						  for (int k=0;k<numFeatures;k++) {
	
							  String val[]=temp[k].split("\""); 
							  if ((val.length)>1) {
								  matrix[i][k]=Double.parseDouble(val[1]); 
							  }
							  else{
								  matrix[i][k]=Double.parseDouble(val[0]);   
							  }
							  
						  }
						  i++;				 
					  }
				  }
				  else { //calcule el punto medio para cada persona
					  int i=0;
					  int l=0;
					  int k=0;
					  for (int p=0;p<totalPersons;p++){				  
							//  Cuantas veces una persona esta en un rango de frames)  es decir, cuente cuantos puntos se encuentran en la matriz para la persona p,
						    //dado un frame inicial (f)  y el final(f+windowsize).
						   // positionPerson is a matrix totalFrames X totalPersons 
						  int ttp=getTotalPointsPerson(positionPersons,f,totalFrames,windowSize,p);
						  l=0;
						  while (l<ttp && (strLine = br.readLine()) != null)   { // recuerde que el archivo solo contiene las personas que aparecen en los frames, 
							  													// dado el rango de frames (inicial -f)  y final (f+sizewindow)
							  temp=strLine.split(",");
							  for ( k=0;k<numFeatures;k++) {
		
								  String val[]=temp[k].split("\""); 
								  matrix[i][k]=matrix[i][k] +Double.parseDouble(val[1]); 
							  }
							  l++;				 
						  }
						  if (l>0){
							  for ( k=0;k<numFeatures;k++) {
								  matrix[i][k]= matrix[i][k]/(l*1.0);
							  }
							  i++;
						  }
					  }
				  }
				  in.close();
		}catch (Exception e){//Catch exception if any
		  System.err.println("Error: " + e.getMessage());
		  }
		return matrix;
	}
	
	// crea archivo para enviar al dbscan y tambien retorna una matriz  binaria indicando donde hay 3d posicion  para una persona, 0 de lo contrario.
	// The matrix is totalFrames X totalPersons 
	public static int[][] generateFile(String inputDir,String featuresNameFile,int f,int totalPersons,int totalFrames ,int numFeatures,int window,String header){
	
		Double framesPerSec=30.0; //total frames in one second
		f=f+1;
		int [][] positions=Groups.createIntMatrix(totalFrames,totalPersons);

		try{
			  // Create file 
			  FileWriter fstream = new FileWriter(featuresNameFile);
			  BufferedWriter out = new BufferedWriter(fstream);
			  out.write(header+"\n");
			  for (int j=0;j<totalPersons;j++){
				  FileInputStream fstreamIn = new FileInputStream(inputDir+"/"+"person_"+(j+1)+".txt");
				  // Get the object of DataInputStream
				  DataInputStream in = new DataInputStream(fstreamIn);
				  BufferedReader br = new BufferedReader(new InputStreamReader(in));
				  String strLine= br.readLine(); // read first line.
				  String temp[];
				  int i=0;
				  //Read File Line By Line until find the line corresponding to the frame.
				  while (i<(f-1)) {
					  if ((strLine = br.readLine()) != null){ 				  
					  	i++;
				 	}
				  }

				  
				  int k=0;
				  int timeStep=f;
				  while ((k<window) && ((strLine = br.readLine()) != null))   {
					  if (strLine != null){
						  temp=strLine.split("\\s+");
						  
						  if (temp[7].equals("1.0")) {  // Person in frame.
							  positions[i][j]=1;
							  int p;
							  for (p=1;p<numFeatures;p++){
								  out.write(temp[p]+",");
							  }
							  out.write(temp[p]+"\n");
/*	TS						  timeStep=f+k;
							  out.write(temp[p]+",");
							  Double frac=(timeStep % window)/framesPerSec;
							  out.write(frac+"\n"); //write timeStep.													  
*/						  }
						  else{
							  positions[i][j]=0; 
						  }
						  i++;
						  k++;						  
					  }
				  }
				  
				  //Close the input stream
				  in.close();
				  //System.out.println(f+" "+k);
			  }
			  //Close the output stream
			  out.close();
			  }catch (Exception e){//Catch exception if any
				  System.err.println("Error: " + e.getMessage());
			  }
			  		
		return positions;		
	}
	
	// Cuente el numero de personas que aparecen en el rango de frames.
	public static int countPersons(int [][] positionPersons,int f,int totalFrames,int windowSize,int totalPersons){
		int count=0;
		for (int p=0;p<totalPersons;p++)	
			for (int i=f;i<f+windowSize && i <totalFrames;i++){
				
				if (positionPersons[i][p]==1){
					count++;
					break;
				}
			}
		return count;
	}
	
	
	// Cuente cuantos puntos  se encuentran en la matriz para la persona p. 
	// Cuantas veces la persona "p" aparece en un rango de frames(desde f hasta f+window).
	// positionPerson is a matrix totalFrames X totalPersons 
	public static int getTotalPointsPerson(int [][] positionPersons,int f,int totalFrames,int windowSize,int p){
		int count=0;
		
		for (int i=f;i<f+windowSize && i <totalFrames;i++){
			if (positionPersons[i][p]==1){
				count++;
			}
		}
		return count;
	}
	

	
	public static Double findMinEuclideanDist(Double [][] matrix,int numFeatures){
		if (matrix.length>1) {
			int numDistances=Groups.comb2(matrix.length);
			Double [][] vector=Groups.createDoubleMatrix(1,numDistances);
			Double val;
			int p=0;
			
			for (int i=0;i<matrix.length;i++){
	
				for (int j=i+1;j<matrix.length;j++){
					Double sum=0.0;
					for (int k=0;k<numFeatures;k++){
						 val=matrix[i][k]-matrix[j][k];
						 sum = sum + Math.pow(val,2);
					}
					vector[0][p]= Math.sqrt(sum);
					p++;
				}
			}
			Groups.printMatrix(vector,1,numDistances);
			return Groups.min(vector[0]);
		}
		else
			return 0.0;
	}
	
	
	public static DBScan setCluster(Double eps,int k){
		DBScan clusterer = new DBScan();   // new instance of clusterer
		clusterer.setEpsilon(eps);
		clusterer.setMinPoints(k);
		clusterer.setDatabase_Type("weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase");
		clusterer.setDatabase_distanceType("weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject");
		return clusterer;
	}
	
	
	public static int [][] setResults(int [][] matrixResultCluster,Instances data,DBScan clusterer,int [][] positionPersons,int f,int totalFrames,int totalPersons,int windowSize){
		int k=0;
		int count=0;
		int i=0;
		int j=0;
		int person;

		Enumeration<Instance> enumeration = data.enumerateInstances();
		while (enumeration.hasMoreElements()) {
			Instance instance = enumeration.nextElement();
			try {
				k++;
				boolean notFound=true;
				for (j=0;j<totalPersons && notFound;j++){
					for (i=f;(i<(f+windowSize) && (i < totalFrames)) ;i++){
						if (positionPersons[i][j]==1) {
							count++;
							if (count==k) {
								notFound=false;
								count=0;
								break;
							}
						}
					}					
				}
				
				j--;

				int g = clusterer.clusterInstance(instance);

				matrixResultCluster[i][j]=g;

			}
			catch (Exception e) {
//				System.out.println(instance.toString()+" = "+"NOISE");

				matrixResultCluster[i][j]=-1;
			}
		}
		return matrixResultCluster;
	}

	


	public static void main(String[] args) throws Exception {
		
		JSAP jsap = new JSAP();
		Switch helpSwitch = new Switch("help")
				.setShortFlag('h')
				.setLongFlag("help");
		FlaggedOption tracks = new FlaggedOption("3dtracks")
				.setDefault("")
				.setRequired(true)
				.setLongFlag("3dtracks");
		FlaggedOption outfile = new FlaggedOption("out")
				.setDefault("")
				.setRequired(true)
				.setLongFlag("out");
		jsap.registerParameter(helpSwitch);
		jsap.registerParameter(tracks);
		jsap.registerParameter(outfile);

		JSAPResult config = jsap.parse(args);
		if (config.getBoolean("help") || "".equals(config.getString("3dtracks"))
				|| "".equals(config.getString("out"))) {
			System.out.print("java -jar 3d-clustering.jar ");
			System.out.println(jsap.getUsage());
			return;
		}
		
		String inputDir = config.getString("3dtracks");
		String outputName = config.getString("out");
		
		// Change these lines to the appropriate movie
//		String movieName = "Y204";
//		String inputDir="/Users/trananh/Desktop/psi-y2/data-Y2/"+movieName+"/3d-tracks";
//		String outputDir="/Users/trananh/Desktop/psi-y2/data-Y2/"+movieName+"/3d-clusters";
		
		
		String feauturesNameFile="features.csv";
//		String feauturesNameFile="/sda1/home/mhmejia/Documents/test4pointst.csv";
		/* TS		int numFeatures=4;
		String header="x,y,z,t";
		int windowSize=60;*/
		
		int numFeatures=3;
		String header="x,y,z";
		int windowSize=1;

		boolean getAveragePoints = windowSize!=1;
		
/*		String inputDir="/sda1/home/mhmejia/PSI/europeMovies/groundTrue3d/newspaper/3d_tracks";
		String outputDir="/sda1/home/mhmejia/PSI/europeMovies/groundTrue3d/newspaper";
		String outputName="/sda1/home/mhmejia/Documents/clusterResults/newspaperClusterST"+windowSize+".csv";
		String headerTitle="frame Number;Woman in DRESS;Man in BLACK pants;Man in SHORTS;Woman in Jeans and White;Woman in Yellow;Woman in White skirt";	//newspaper	 																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																						
		int totalFrames=168;   // newspaper
		int totalPersons=6;
		int typeTranslation=1;*/
		
		
/*		String inputDir="/sda1/home/mhmejia/PSI/europeMovies/groundTrue3d/map/3d_tracks";
		String outputDir="/sda1/home/mhmejia/PSI/europeMovies/groundTrue3d/map";
		String outputName="/sda1/home/mhmejia/Documents/clusterResults/mapClusterST"+windowSize+".csv";
		String headerTitle="Frame Number;Man in STRIPED  shirt;Man in GREEN shirt;Women in WHITE;Women in SHORTS;Women in GRAY;Women in short DRESS"; //map																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																									
		int totalFrames=420;	// map	
		int totalPersons=6;
		int typeTranslation=2;*/																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																	
		
		
		int totalPersons=countFiles(inputDir);
		if (totalPersons>0)		{
			String person0File=inputDir+"/person_1.txt";
			int totalFrames=countLines(person0File)-1;
			int typeTranslation=totalPersons;
			String headerTitle=Groups.getHeader(typeTranslation);
						

			int [][] matrixResultCluster=Groups.createIntMatrix(totalFrames,totalPersons);
			matrixResultCluster=Groups.Initialize(matrixResultCluster,totalFrames,totalPersons);  //poner en -2 toda la matriz
			
			for(int f=1; f<=totalFrames; f+=windowSize){
				int [][] positionPersons=Groups.createIntMatrix(windowSize,totalPersons);
	            // positionPersons is a marix of totalFrames x total persons.Metodo generateFile  Crea un archivo cuyo nombre esta en la variable "featuresNameFile" para enviar al dbscan y 
	            // tambien retorna una matriz  binaria indicando donde hay 3d posicion  para una persona, 0 de lo contrario.
				 // TS positionPersons=generateFile(inputDir,feauturesNameFile,f-1,totalPersons,totalFrames,numFeatures-1,windowSize,header); 
	             positionPersons=generateFile(inputDir,feauturesNameFile,f-1,totalPersons,totalFrames,numFeatures,windowSize,header); 

	             int totPersonsWindow=countPersons(positionPersons,f-1,totalFrames,windowSize,totalPersons);
	             if (totPersonsWindow>0) {
		             //Groups.printMatrix(positionPersons,positionPersons.length,totalPersons);
		     		 Instances data =readData(feauturesNameFile);  // read the file already generated. It has the information going to the cluster.
		     		 
		     		 // read the file already generated an it is obtained an average of the points, the average points are in a matrix. 
		     		 // Each point correspond to a person location.(actual or average)
		     		 Double[][] matrix=readDataCSV( feauturesNameFile, numFeatures,positionPersons,f-1,totalFrames,windowSize,totalPersons,getAveragePoints);
		     		 //Groups.printMatrix(matrix,matrix.length,numFeatures);
		     		 //System.out.println("matrix.length="+matrix.length);
		     		 Double minD=findMinEuclideanDist(matrix,numFeatures); // there are numFeatures +1 because one additional feature is the timeStep or frame number
		     		 //System.out.println("min="+minD);
		     		 
		     		DBScan clusterer=setCluster(minD,2);
		     		clusterer.buildClusterer(data);
		    		
		    		//System.out.println(clusterer.toString());
		    		matrixResultCluster=setResults(matrixResultCluster,data,clusterer,positionPersons,f-1,totalFrames,totalPersons,windowSize);
		    		//Groups.printMatrix(matrixResultCluster,matrixResultCluster.length,totalPersons);
	             }
	        }
	
			//Groups.printMatrix(matrixResultCluster,totalFrames,totalPersons);
			Set uniqueGroups= new HashSet();
	        uniqueGroups=Groups.findUniqueGroups(matrixResultCluster,totalFrames);
	        //System.out.println(uniqueGroups);
	        int [][] newMatrix=Groups.setIdGroups(matrixResultCluster, uniqueGroups,totalFrames,totalPersons);
	        //Groups.printMatrix(newMatrix,totalFrames,totalPersons);
	
	        //Groups.printAllFrames( newMatrix,totalFrames,totalPersons, uniqueGroups,typeTranslation);
	        
	        String [][] matrixGroupsDiffId=Groups.setIdGroupsDiffId(newMatrix, uniqueGroups,totalFrames,totalPersons);
	        Groups.printAllFrames( matrixGroupsDiffId,totalFrames,totalPersons, uniqueGroups,typeTranslation);
	        Groups.writeFile(outputName,headerTitle,matrixGroupsDiffId,totalFrames,totalPersons, uniqueGroups,typeTranslation);

		}
		 System.out.println("DONE");
	}
	
}



